/*
 * Copyright 2012-2013 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid.column;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.Messages;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.*;

import finbudget.client.ui.table.Record;
import finbudgettable.client.ui.grid.CellGrid;
import finbudgettable.client.ui.grid.ValueHolder;
import finbudgettable.client.ui.grid.filter.Filter;
import finbudgettable.client.ui.grid.filter.FilterChangeEvent;
import finbudgettable.client.ui.grid.filter.FilterChangeHandler;
import finbudgettable.client.ui.grid.filter.FilterResolverRepository;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class ColumnMenuImpl implements ColumnMenu {

    interface Strings extends Messages {

        @DefaultMessage("Сортировать Я → А")
        String sortDesc();

        @DefaultMessage("Сортировать А → Я")
        String sortAsc();

        @DefaultMessage("Показать")
        String show();

        @DefaultMessage("Скрыть")
        String hide();

        @DefaultMessage("Удалить фильтр ({0})")
        String clearFilter(String filter);

        @DefaultMessage("Удалить фильтр (<unknown>)")
        String unknownFilter();
    }

    private static final Strings STRINGS = GWT.create(Strings.class);

    private final CellGrid grid;
    private GridColumn<?> column;
    private final FilterPanel filterPanel;

    private MenuBar menu;
    private PopupPanel popupPanel;

    private MenuItem hideMenuItem;
    private MenuItemSeparator filterMenuSeparator;
    private MenuItem filterMenuItem;

    private final Command sortDescCommand = new Command() {
        @Override
        public void execute() {
            grid.sortColumn(column, false);
            popupPanel.hide();
        }
    };

    private final Command sortAscCommand = new Command() {
        @Override
        public void execute() {
            grid.sortColumn(column, true);
            popupPanel.hide();
        }
    };

    private final Command clearFilterCommand = new Command() {
        @Override
        public void execute() {
            grid.getFilter().clearFilters(column.getFieldName());
            grid.filter();
            popupPanel.hide();
        }
    };

    private final Command hideColumnCommand = new Command() {
        @Override
        public void execute() {
            if (grid.getColumnCount() > 1) {
                grid.hideColumn(column);
                popupPanel.hide();
            }
        }
    };

    public ColumnMenuImpl(CellGrid grid, FilterPanel filterPanel) {
        this.grid = grid;
        this.filterPanel = filterPanel;
    }

    @Override
    public GridColumn<?> getColumn() {
        return column;
    }

    private final Map<Column<Record, ?>, MenuItem> columnMenuItems = new HashMap<Column<Record, ?>, MenuItem>();

    private MenuBar createColumnsMenu() {
        GridMenuBar columns = new GridMenuBar();
        columns.setAutoOpen(true);
        columnMenuItems.clear();
        for (final GridColumn<?> column : grid.getAllColumns()) {
            if (!column.canBeHidden()) {
                continue;
            }
            String title = grid.getHeader(column).getValue().toString();
            MenuItem columnMenuItem = columns.addItem(
                    new CheckBoxMenuItem(title, new ValueHolder<Boolean>() {
                        @Override
                        public void setValue(Boolean value) {
                            if (!value) {
                                grid.hideColumn(column);
                            } else {
                                grid.showColumn(column);
                            }

                            boolean lastColumn = grid.getColumnCount() == 1;

                            if (!lastColumn) {
                                hideMenuItem.setEnabled(grid.isColumnVisible(ColumnMenuImpl.this.column));
                            } else {
                                hideMenuItem.setEnabled(false);
                            }

                            if (lastColumn) {
                                MenuItem menuItem = columnMenuItems.get(grid.getColumn(0));
                                if (menuItem != null) {
                                    menuItem.setEnabled(false);
                                }
                            } else {
                                for (MenuItem menuItem : columnMenuItems.values()) {
                                    menuItem.setEnabled(true);
                                }
                            }
                        }

                        @Override
                        public Boolean getValue() {
                            return grid.isColumnVisible(column);
                        }
                    }, new CheckBoxMenuItem.ClickHandler() {
                        @Override
                        public boolean onClick(boolean checked) {
                            if (checked) {
                                grid.showColumn(column);
                                return true;
                            } else {
                                return grid.hideColumn(column);
                            }
                        }
                    }
                    )
            );
            columnMenuItems.put(column, columnMenuItem);
        }
        return columns;
    }

    private MenuItem colMenu;
    private Date lastClosedMoment;
    private ColumnMenuContainer columnMenuContainer;

    private void ensureCreate() {
        if (popupPanel == null) {
            popupPanel = new PopupPanel(true, true) {
            	@Override
            	public void hide(boolean autoClosed) {
            		super.hide(autoClosed);
            		lastClosedMoment = new Date();
            		if (columnMenuContainer != null) {
            			columnMenuContainer.onMenuClosed();
            		}
            	}
            };
            VerticalPanel verticalPanel = new VerticalPanel();
            popupPanel.add(verticalPanel);
            menu = new MenuBar(true);
            menu.setAutoOpen(true);
            menu.addStyleName(GridColumn.RESOURCES.style().bColumnMenu());
            menu.addItem(STRINGS.sortAsc(), sortAscCommand);
            menu.addItem(STRINGS.sortDesc(), sortDescCommand);

            menu.addSeparator().setStylePrimaryName(GridColumn.RESOURCES.style().bColumnMenuSeparator());

            colMenu = menu.addItem(STRINGS.show(), (Command) null);

            hideMenuItem = menu.addItem(STRINGS.hide(), hideColumnCommand);

            filterMenuSeparator = menu.addSeparator();
            filterMenuSeparator.setStylePrimaryName(GridColumn.RESOURCES.style().bColumnMenuSeparator());
            filterMenuItem = menu.addItem(STRINGS.clearFilter(""), clearFilterCommand);

            verticalPanel.add(menu);
            if (filterPanel != null) {
                filterPanel.addFilterChangeHandler(new FilterChangeHandler() {
                    @Override
                    public void onFilterChange(FilterChangeEvent event) {
                        Filter filter = event.getFilter();
                        if (filter != null) {
                            grid.getFilter().setFilter(column.getFieldName(), event.getFilter());
                        } else {
                            grid.getFilter().clearFilters(column.getFieldName());
                        }
                        grid.filter();
                        popupPanel.hide();
                    }
                });
                menu.addSeparator().setStylePrimaryName(GridColumn.RESOURCES.style().bColumnMenuSeparator());
                verticalPanel.add(filterPanel.asWidget());
                filterPanel.asWidget().getElement().getParentElement().getStyle().setPaddingTop(0, Unit.PX);
                filterPanel.asWidget().getElement().getParentElement().getStyle().setPaddingLeft(17, Unit.PX);
                filterPanel.asWidget().getElement().getParentElement().getStyle().setPaddingBottom(15, Unit.PX);
                filterPanel.asWidget().getElement().getParentElement().getStyle().setPaddingRight(18, Unit.PX);
            }
        }
        colMenu.setSubMenu(createColumnsMenu());
        refreshFilterMenu();
    }

    private void refreshFilterMenu() {
        Filter filter = grid.getFilter().getFilter(column.getFieldName());
        boolean columnFiltered = filter != null;
        if (columnFiltered) {
            String filterDescription = FilterResolverRepository.resolve(filter);
            if (filterDescription != null) {
                filterMenuItem.setText(STRINGS.clearFilter(filterDescription));
            } else {
                filterMenuItem.setText(STRINGS.unknownFilter());
            }
        }
        filterMenuItem.setVisible(columnFiltered);
        filterMenuSeparator.setVisible(columnFiltered);
        if (filterPanel != null) {
            filterPanel.setFilter(filter);
        }
        // This block is used to eliminate border-spacing effect. The effect is that in case filterMenuItem is not shown then
        // additional 6px (2 * 3px) added between last menu item and separator.
        if (!columnFiltered) {
            filterMenuItem.getElement().getParentElement().getStyle().setDisplay(Display.NONE);
            filterMenuSeparator.getElement().getParentElement().getStyle().setDisplay(Display.NONE);
        } else {
        	filterMenuItem.getElement().getParentElement().getStyle().clearDisplay();
        	filterMenuSeparator.getElement().getParentElement().getStyle().clearDisplay();
        }
    }

    /**
     * (x, y) is the top left corner coordinate of the menu to be shown.
     */
    @Override
    public void show(int x, int y, GridColumn<?> column, Filter filter, ColumnMenuContainer columnMenuContainer) {
    	if (lastClosedMoment != null) {
    		if (new Date().getTime() - lastClosedMoment.getTime() < 400L) {
    			return;
    		}
    	}
        this.column = column;
    	this.columnMenuContainer = columnMenuContainer;
        ensureCreate();
        popupPanel.setPopupPosition(x, y);
        popupPanel.show();
        x = x - popupPanel.getOffsetWidth();	// new correct position
        if (x < 0) {
        	x = 0;
        }
        popupPanel.setPopupPosition(x, y);

        boolean manyColumns = grid.getColumnCount() > 1;
        hideMenuItem.setEnabled(manyColumns);

        boolean lastColumn = grid.getColumnCount() == 1;
        if (lastColumn) {
            Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
                @Override
                public void execute() {
                    columnMenuItems.get(grid.getColumn(0)).setEnabled(false);
                }
            });
        }
    }

    public void hide() {
        popupPanel.hide();
    }

}
